package com.jsbb.util;

import com.jsbb.dto.tool.ApiParamDto;
import com.jsbb.dto.tool.ApiDto;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import java.util.*;
import java.util.stream.Stream;

/**
 * Swagger工具类
 *
 * @author jsbb
 */
public class SwaggerUtils {

    /**
     * 封装swagger接口数据
     *
     * @param api
     * @return
     */
    public static List<ApiDto> getSwaggerData(String api) {
        List<ApiDto> list = new ArrayList<>();
        Map<String, Object> swaggerApiMap = new RestTemplate().getForObject(api + "/v3/api-docs", Map.class);
        Map<String, Map<String, Object>> paths = (Map<String, Map<String, Object>>) swaggerApiMap.get("paths");
        if (paths == null) {
            return list;
        }
        Map<String, Map<String, Object>> definitions = (Map<String, Map<String, Object>>) ((Map<String, Object>) swaggerApiMap.get("components")).get("schemas");
        Iterator<Map.Entry<String, Map<String, Object>>> pathIterator = paths.entrySet().iterator();
        while (pathIterator.hasNext()) {
            Map.Entry<String, Map<String, Object>> path = pathIterator.next();
            String url = path.getKey();
            Iterator<Map.Entry<String, Object>> methodIterator = path.getValue().entrySet().iterator();
            while (methodIterator.hasNext()) {
                Map.Entry<String, Object> methodEntry = methodIterator.next();
                Map<String, Object> methodContent = (Map<String, Object>) methodEntry.getValue();
                ApiDto apiDto = new ApiDto();
                apiDto.setUrl(url);
                apiDto.setMethod(methodEntry.getKey());
                apiDto.setTitle(String.valueOf(methodContent.get("summary")));
                apiDto.setTags(String.valueOf(((List) methodContent.get("tags")).get(0)));
                apiDto.setRequestList(getRequestList(methodContent, definitions));
                apiDto.setResponseList(getResponseList(methodContent, definitions));
                list.add(apiDto);
            }
        }
        return list;
    }

    /**
     * 获取请求参数
     *
     * @param methodContent
     * @param definitions
     * @return
     */
    private static List<ApiParamDto> getRequestList(Map<String, Object> methodContent, Map<String, Map<String, Object>> definitions) {
        List<ApiParamDto> requestList = new ArrayList<>();
        List<LinkedHashMap> parameters = (ArrayList) methodContent.get("parameters");
        Map<String, Object> requestBody = (Map<String, Object>) methodContent.get("requestBody");
        if (parameters != null) {
            for (Map<String, Object> parameter : parameters) {
                Map<String, Object> schema = (Map<String, Object>) parameter.get("schema");
                if (schema == null) {
                    ApiParamDto apiParamDto = new ApiParamDto();
                    apiParamDto.setName((String) parameter.get("name"));
                    apiParamDto.setType((String) parameter.get("type"));
                    apiParamDto.setComment((String) parameter.get("description"));
                    apiParamDto.setRequired((boolean) parameter.get("required") ? "是" : "否");
                    requestList.add(apiParamDto);
                } else {
                    String ref = (String) schema.get("$ref");
                    String type = (String) schema.get("type");
                    String originalRef = ref != null ? ref.substring(ref.lastIndexOf("/") + 1) : null;
                    if (originalRef != null) {
                        analyzingRef(definitions, originalRef, requestList, null, true);
                    }
                    if (type != null) {
                        ApiParamDto apiParamDto = new ApiParamDto();
                        apiParamDto.setName((String) parameter.get("name"));
                        apiParamDto.setType(type);
                        apiParamDto.setComment((String) parameter.get("description"));
                        apiParamDto.setRequired((boolean) parameter.get("required") ? "是" : "否");
                        requestList.add(apiParamDto);
                    }
                }
            }
        }
        if (requestBody != null) {
            Map<String, Object> schema = (Map<String, Object>) ((Map<String, Object>) ((Map<String, Object>) requestBody.get("content")).get("application/json")).get("schema");
            String ref = (String) schema.get("$ref");
            String originalRef = ref != null ? ref.substring(ref.lastIndexOf("/") + 1) : null;
            if (originalRef != null) {
                analyzingRef(definitions, originalRef, requestList, null, true);
            }
        }
        return requestList;
    }

    /**
     * 获取响应参数
     *
     * @param methodContent
     * @param definitions
     * @return
     */
    private static List<ApiParamDto> getResponseList(Map<String, Object> methodContent, Map<String, Map<String, Object>> definitions) {
        List<ApiParamDto> responseList = new ArrayList<>();
        Map<String, Object> responses = (Map<String, Object>) methodContent.get("responses");
        Map<String, Object> responses200 = (Map<String, Object>) responses.get("200");
        Map<String, Object> content = (Map<String, Object>) responses200.get("content");
        if (content == null) {
            return responseList;
        }
        Map<String, Object> schema = (Map<String, Object>) ((Map<String, Object>) content.get("*/*")).get("schema");
        if (schema != null) {
            String ref = (String) schema.get("$ref");
            String originalRef = ref != null ? ref.substring(ref.lastIndexOf("/") + 1) : null;
            if (originalRef != null) {
                analyzingRef(definitions, originalRef, responseList, null, false);
            }
        }
        return responseList;
    }

    /**
     * 解析对象
     *
     * @param definitions
     * @param originalRef
     * @param paramList
     * @param name
     * @param required
     */
    public static void analyzingRef(Map<String, Map<String, Object>> definitions, String originalRef, List<ApiParamDto> paramList, String name, boolean required) {
        if (name != null) {
            String[] arr = name.split("\\.");
            int level = 4;
            if (Stream.of(arr).distinct().count() < arr.length || arr.length == level) {
                return;
            }
        }
        Map<String, Object> obj = definitions.get(originalRef);
        if (obj != null) {
            List<String> requiredList = (List<String>) obj.get("required");
            Map<String, Object> properties = (Map<String, Object>) obj.get("properties");
            if (properties != null) {
                Iterator<Map.Entry<String, Object>> propertiesIterator = properties.entrySet().iterator();
                while (propertiesIterator.hasNext()) {
                    Map.Entry<String, Object> propertiesEntry = propertiesIterator.next();
                    Map<String, Object> property = (Map<String, Object>) propertiesEntry.getValue();
                    ApiParamDto apiParamDto = new ApiParamDto();
                    if (!StringUtils.hasText(name)) {
                        apiParamDto.setName(propertiesEntry.getKey());
                    } else {
                        apiParamDto.setName(name + "." + propertiesEntry.getKey());
                    }
                    apiParamDto.setType((String) property.get("type"));
                    apiParamDto.setComment((String) property.get("title"));
                    if (required) {
                        apiParamDto.setRequired(requiredList != null && requiredList.contains(apiParamDto.getName()) ? "是" : "否");
                    }
                    paramList.add(apiParamDto);
                    String propertyRef = (String) property.get("$ref");
                    String dataOriginalRef = propertyRef != null ? propertyRef.substring(propertyRef.lastIndexOf("/") + 1) : null;
                    if (dataOriginalRef == null) {
                        Map<String, Object> items = (Map<String, Object>) property.get("items");
                        if (items != null) {
                            String itemsRef = (String) items.get("$ref");
                            String itemsOriginalRef = itemsRef != null ? itemsRef.substring(itemsRef.lastIndexOf("/") + 1) : null;
                            analyzingRef(definitions, itemsOriginalRef, paramList, apiParamDto.getName(), required);
                        }
                    } else {
                        apiParamDto.setType("object");
                        analyzingRef(definitions, dataOriginalRef, paramList, apiParamDto.getName(), required);
                    }
                }
            }
        }
    }
}
